import json
import threading
import time
import datetime

import  requests
from fastapi import  FastAPI

#-----------------------------
# install modbus libs
# pip3 install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple  pymodbus==2.5.3  --低版本
#-----------------------------
from pymodbus.client.sync import ModbusSerialClient as ModbusClient
import serial, serial.tools.list_ports

class TZnDecoder:
    def __init__(self):
        pass
    @staticmethod
    def decode(var , regs , exInfo=None):
        status = 0
        value = 0
        try:
            funType = var['funType']
            if funType=='Bit':
                status , value = TZnDecoder.decodeBit(var , regs , exInfo)
            elif funType=='U16':
                status , value = TZnDecoder.decodeU16(var , regs , exInfo)
            elif funType=='I16':
                status , value = TZnDecoder.decodeI16(var , regs , exInfo)
            elif funType=='U32':
                status , value = TZnDecoder.decodeU32(var , regs , exInfo)
            elif funType=='I32':
                status , value = TZnDecoder.decodeI32(var , regs , exInfo)
            elif funType=='I32LH':
                status , value = TZnDecoder.decodeI32LH(var , regs , exInfo)
            elif funType=='U64':
                status , value = TZnDecoder.decodeU64(var , regs , exInfo)
            elif funType=='I64':
                status , value = TZnDecoder.decodeI64(var , regs , exInfo)
            elif funType=='U32Time':
                status , value = TZnDecoder.decodeU32Time(var , regs , exInfo)
            elif funType=='PGU':
                status , value = TZnDecoder.decodePgu(var , regs , exInfo)

            if status == 1:
                rate = var['rate']
                if not rate == 1 :
                    value = value * 1.0 * rate
                rate1 = var['rate1']
                if not rate1 == 1 :
                    value = value * 1.0 * rate1
                decLen = var['decLen']
                if decLen>0 :
                    #value = float(('%.' + str(decLen) + 'f') % value)
                    value = round( value , decLen)
            else:
                print('decode error:' , var , regs)
        except Exception as er:
            print(er)
        return  status , value
    @staticmethod
    def decodeBit(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            bitStart = var['bitStart']
            mask = 0x01 << bitStart
            v1 = regs[0] & mask
            if v1==0 :
                res = 0
            else:
                res = 1
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU16(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            res = regs[0]
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI16(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            v = regs[0]
            if v< 0x7fff:
                res = v
            else:
                res = -1*( (v ^ 0xffff) + 1 )
                #res =  -1 * ( (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU32(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            vH = regs[0]
            vL = regs[1]
            res = (vH << 16 ) + vL
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI32(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            vH = regs[0]
            vL = regs[1]
            v = (vH << 16 ) + vL
            if vH < 0x7fff :
                res = v
            else:
                res = -1 * ( ( v ^ 0xffffffff) +1  )
                #res =  -1 * (  (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI32LH(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            vL = regs[0]
            vH = regs[1]
            v = (vH << 16 ) + vL
            if vH < 0x7fff :
                res = v
            else:
                res = -1 * ( ( v ^ 0xffffffff) +1  )
                #res =  -1 * (  (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU64(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            v0 = regs[2]
            v1 = regs[3]
            v2 = regs[0]
            v3 = regs[1]
            v = v0<<48 + v1<<32 + v2<<16 + v3
            res = v
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeI64(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            v0 = regs[2]
            v1 = regs[3]
            v2 = regs[0]
            v3 = regs[1]
            v = v0<<48 + v1<<32 + v2<<16 + v3
            if v0 < 0x7fff:
                res = v
            else:
                res = -1 * ( ( v ^ 0xffffffffffffffff) +1  )
                #res =  -1 * (  (~v) + 1)
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodePgu(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            bitStart = var['bitStart']
            bitLen = var['bitLen']
            if bitLen == 1 :
                res = regs[bitStart]
            elif bitLen == 2 :
                res = (regs[bitStart]<<16) + regs[bitStart+1]
            status = 1
        except Exception as er:
            print(er)
        return  status , res
    @staticmethod
    def decodeU32Time(var , regs , exInfo=None):
        status = 0
        res = 0
        try:
            rH = regs[0]
            rL = regs[1]
            varValue = (rH << 16 ) + rL
            varValue = varValue - 8 * 3600
            res =  varValue
            #varDisp = (datetime.datetime.fromtimestamp(varValue)).strftime("%Y-%m-%d %H:%M:%S")
            status = 1
        except Exception as er:
            print(er)
        return  status , res
class TBus:
    def __init__(self):
        self.ter = None
        self.busInfo={}
        self.devices={}
        self.vars={}
        self.varValues={}
        self.actived = 0
        self.inputThread = None
    def startBus(self):
        pass
class TBusModbus(TBus):
    def __init__(self):
        super().__init__()
        self.client = ModbusClient()
        self.tasks = []
    def startBus(self):
        print('start modbus bus')
        try:
            self.stopBus()
            self.inputThread = threading.Thread( target=self.inputHandler , daemon=True)
            self.inputThread.start()
        except Exception as er:
            print(er)
    def stopBus(self):
        print('start modbus bus')
        try:
            if self.actived:
                self.actived = False
            if self.client!=None:
                try:
                    self.client.close()
                except Exception as ee:
                    print(ee)
                self.client = None
        except Exception as er:
            print(er)
    def inputHandler(self):
        print('start modbus bus')
        try:
            comPort = self.busInfo['com_port']
            comBaudrate = self.busInfo['com_baudrate']
            comStopBit = self.busInfo['com_stopbits']
            comParity = self.busInfo['com_parity']
            comDataBit = self.busInfo['com_bytesize']
            client = ModbusClient(
                method="rtu" ,
                port= comPort , baudrate=comBaudrate , stopbits=comStopBit, parity= comParity, bytesize=comDataBit ,
                strict= False,
                timeout= 2
                )
            self.client = client
            self.actived = True
            while self.actived :
                try:
                    time.sleep(30)
                    busChangedValues ={}
                    for task in self.tasks:
                        try:
                            tmNow = time.time()
                            task['duration'] = 2
                            #timeFlag = (tmNow - task['lastTime']) >= task['duration']
                            timeFlag = True
                            time.sleep(0.2)

                            if timeFlag :
                                task['lastTime'] = tmNow

                                regStart = task['regStart']
                                regLen = task['regLen']
                                unitId = task['unitId']

                                try:
                                    if regStart in [ 0 , 1000]:
                                        time.sleep(0.5)
                                    elif regStart in [35300,35304]:
                                        time.sleep(1)
                                        print('reg 3530X')
                                    result = self.client.read_holding_registers(regStart, regLen, unit=unitId)
                                    if result.isError():
                                        print('collect faild', regStart, result)
                                        time.sleep(1)
                                    else:
                                        vars = task['vars']
                                        regs = list(result.registers)
                                        changedVars = self.getChangedVars(vars , regs)
                                        for v in changedVars.values():
                                            busChangedValues[v['k']] = v
                                        #self.ter.notify( changedVars)
                                except Exception as erInput:
                                    print(erInput)
                        except Exception as erTask:
                            print(erTask)
                    if len(busChangedValues.keys())>0:
                        self.ter.postVars(busChangedValues)
                except Exception as err :
                    print(err)

        except Exception as er:
            print(er)
    def getChangedVars(self , vars , regs):
        res = {}
        try:
            for var in vars :
                try:
                    status , value    = TZnDecoder.decode( var , regs)
                    if status == 1 :
                        v0 = var['values']['v']
                        t = var['values']['t']
                        tmNow = time.time()
                        changedFlag = t == 0
                        if not changedFlag:
                            if value!=v0:
                                if var['varType'] == 'a' :
                                    if v0 == 0 :
                                        changedFlag = True
                                    else:
                                        dlt = abs(value - v0)
                                        dltRate = dlt / abs(v0)
                                        if dltRate>=0.005:
                                            changedFlag = True
                                else:
                                    changedFlag = True

                        if changedFlag:
                            var['values']['v0'] = var['values']['v']
                            var['values']['regs'] = regs
                            var['values']['v'] = value
                            var['values']['t'] = tmNow
                            var['values']['d'] = str(value) + var['varUnit']
                            if var['funType'] =='U32Time':
                                var['values']['d'] =  (datetime.datetime.fromtimestamp(value)).strftime("%Y-%m-%d %H:%M:%S")
                            #unit = var['varUnit']
                            #if unit !='':
                            #    var['values']['disp'] = var['values']['disp'] + ' '+unit
                            res[var["varKey"]] = var['values']
                except Exception as eer:
                    print(eer)
        except Exception as er:
            print(er)
        return res

class TZn645:
    def __init__(self):
        self.comPort = 'COM8'
        self.comBaudrate = 1200
        self.comParity = 'E'  # serial.PARITY_EVEN ,
        self.comTimeout = 2
        self.serial = serial.Serial()

    def isOpen(self):
        res = False
        try:
            if self.serial != None:
                res = self.serial.isOpen()
        except Exception as er:
            print(er)
        return res

    def open(self):
        try:
            if not self.isOpen():
                self.serial = serial.Serial(
                    self.comPort, self.comBaudrate, parity=self.comParity, timeout=self.comTimeout
                )
        except Exception as er:
            print(er)
        return self.isOpen()

    def close(self):
        try:
            if self.serial != None:
                if self.serial.isOpen():
                    self.serial.close()
                self.serial = None
        except Exception as er:
            print(er)

    def readValue(self, address, dataFlag):
        status = 0
        res = ''
        try:
            sendBytes = self.getSendBytes(address, dataFlag)
            self.serial.write(sendBytes)
            resBytes = self.serial.readline()
            resBytes = resBytes[resBytes.index(0x68):]


            addrBytes = ["{:0>2d}".format((x // 16) * 10 + x % 16) for x in resBytes[1:7]]
            addrBytes.reverse()
            addr = ''.join(addrBytes)

            dataBytes = ["{:0>2d}".format(int(hex(x - 0x33).replace('0x', ''))) for x in resBytes[14:-2]]
            dataBytes.reverse()
            res = "".join(dataBytes)

            #if dataFlag == '0020' :
            #    print(resBytes  , 'v:', res)
            status = 1
        except Exception as er:
            print(er)
        return status, res

    def getSendBytes(self, address, dataFlag):
        res = []
        try:
            res.append(0x68)
            for i in range(6):
                pos = (6 - (i + 1)) * 2
                addr = int(address[pos:pos + 2])
                addr = (addr // 10) * 16 + addr % 10
                res.append(addr)
            res.append(0x68)
            res.append(0x11)
            res.append(0x04)
            for i in range(4):
                di = int(dataFlag[i]) + 0x33
                res.append(di)
            c = 0
            for i in res:
                c = c + i
                c = c % 256
            res.append(c)
            res.append(0x16)
        except Exception as er:
            print(er)
        return bytearray(res)
class TBus645(TBus):
    def __init__(self):
        super().__init__()
        self.client645 = None
    def startBus(self):
        try:
            self.stopBus()
            self.client645 = TZn645()
            self.client645.comPort = self.busInfo['com_port']
            self.client645.comBaudrate = self.busInfo['com_baudrate']
            self.client645.comParity = self.busInfo['com_parity']
            self.client645.open()
            self.inputThread = threading.Thread( target=self.executeHandler , daemon=True)
            self.inputThread.start()
        except Exception as er:
            print(er)
    def stopBus(self):
        try:
            self.actived = 0
            if self.client645!=None :
                openFlag = self.client645.isOpen()
                if openFlag :
                    self.client645.close()
                self.client645 = None

        except Exception as er:
            print(er)
    def executeHandler(self):
        try:
            while self.client645!=None and self.client645.isOpen():
                try:
                    time.sleep(1)
                    changedVars ={}
                    for var in self.vars.values():
                        try:
                            tn = time.time()
                            lastTm = var['values']['t']
                            duration = var['duration']
                            #timeoutFlag = ( tn - lastTm )>= duration
                            timeoutFlag = True
                            if timeoutFlag :
                                varKey = var['varKey']
                                varCode = var['varCode']
                                addr = var['addr645']
                                status , valStr = self.client645.readValue( addr , varCode)
                                if status==1 :
                                    rate = var['rate']
                                    rate1 = var['rate1']
                                    decLen = var['decLen']
                                    unit = var['varUnit']
                                    v = float(valStr)
                                    if rate !=1 :
                                        v = v * rate
                                    if rate1!=1 :
                                        v = v * rate1
                                    disp = str(v)
                                    if decLen>0 :
                                        disp = ('%.' + str(decLen) + 'f') % v
                                    disp = disp +' '+ unit
                                    v0 = var['values']['v']
                                    t0 = var['values']['t']
                                    changedFlag = v0 != v   or t0 == 0

                                    if changedFlag:
                                        var['values']['v0'] = v0
                                        var['values']['v'] = round(v,decLen)
                                        var['values']['t'] = time.time()
                                        var['values']['d'] = disp
                                        var['values']['regs'] =[valStr]
                                        changedVars[varKey] = var['values']
                        except Exception as erVar:
                            print(erVar)
                    if len(changedVars.keys())>0:
                        self.ter.postVars(changedVars)
                    time.sleep(120)
                except Exception as err:
                    print(err)
            self.stopBus()
            time.sleep(30)
            self.startBus()
        except Exception as er:
            print(er)
class TTer:
    def __init__(self):
        self.terCode=""
        self.dataUrl = ""
        self.postUrl = ""
        self.apiPort=0

        self.terInfo ={};
        self.buses ={}
        self.tasks={}
        self.devices={}
        self.vars={}

        self.terBusList = []
    def start(self):
        try:
            print('start ter', self.terCode)
            self.load()
            for bus in self.terBusList:
                bus.startBus()
        except Exception as er:
            print(er)
    def load(self):
        try:
            print('load objects...')
            dataUrl = self.dataUrl+"?terCode="+ self.terCode
            rs = requests.get(dataUrl)
            objs = json.loads(rs.content)
            if objs['status'] ==1 :
                self.terInfo = objs['data']['terInfo']
                self.buses = objs['data']['buses']
                self.tasks = objs['data']['tasks']
                self.devices = objs['data']['devices']
                self.vars = objs['data']['vars']
                self.terBusList =[]
                for b in self.buses.values():
                    protocolType = b['protocolType']
                    busCode = b['busCode']
                    bus = None
                    if protocolType == '645':
                        bus = TBus645()
                    elif protocolType == 'modbusRtu':
                        bus = TBusModbus()
                        bus.tasks = []
                        for t in self.tasks.values() :
                            if( busCode==t['busCode']):
                                t['lastTime'] = 0
                                t['vars']=[
                                    self.vars[x] for x in t['varKeys']
                                ]
                                bus.tasks.append(t)
                    if bus != None:
                        bus.ter = self
                        bus.busInfo = b
                        for v in self.vars.values():
                            v["values"] ={
                                "k":v['varKey'],
                                "t":0 ,
                                "v":0 ,
                                "v0":0 ,
                                "d":"",
                                'regs':[]
                            }
                            if busCode == v['busCode']:
                                bus.vars[v['varKey']] = v
                        for d in self.devices.values():
                            if busCode == d['busCode']:
                                bus.devices[d['devCode']] = d
                        self.terBusList.append(bus)
            print(self)
        except Exception as er:
            print(er)
    def postVars(self , vars):
        try:
            varData = []
            for v in vars.values():
                item = {
                    'k': v["k"] ,
                    'id': self.vars[v["k"]]['varId'] ,
                    't':v['t'],
                    'v':v['v'],
                    'd':v['d'],
                    'v0':v['v0'],
                    'regs':v['regs']
                }
                varData.append(item)
            try:
                if len(varData)>0:
                    headers = {
                        'Content-Type': 'application/json',
                    }
                    postData ={
                        "vars":varData
                    }
                    try:
                        resp = requests.post(url=self.postUrl, headers=headers, json= postData)
                        if resp.status_code!=200:
                            print('post data faild.')
                    except Exception as eerr:
                        print('disconnect ')
                        print(eerr)
                    #print(resp.content)
            except Exception as erPost:
                print(erPost)
        except Exception as er:
            print(er)
ter = TTer()
app = FastAPI()
@app.get("/")
async def terRoot():
    return {
        "action":"terminalRoot"
    }
@app.get("/restart")
async def restart():
    ter.start()
    return {
        "action":"terminalRoot"
    }
